package stmlib;
import java.util.*;

public class StmDeque<E> extends Lockable implements Deque<E> {
    final Deque<E> deque;
    public StmDeque (Deque <E> deque, LockManager lm) { this.deque = deque; this.lm = lm; }
    public StmDeque(int numElements) { this(new ArrayDeque<E>(numElements), new LockManager()); }
    public StmDeque () { this(16); }

    public E getFirst() { lm.readLock(); return deque.getFirst(); }
    public E getLast() { lm.readLock(); return deque.getLast(); }
    public E peekFirst() { lm.readLock(); return deque.peekFirst(); }
    public E peekLast() { lm.readLock(); return deque.peekLast(); }

    public boolean add(E e) { addLast(e); return true; }
    public boolean offer(E e) { return offerLast(e); }
    public E remove() { return removeFirst(); }
    public E poll() { return pollFirst(); }
    public E element() { return getFirst(); }
    public E peek() {return peekFirst(); }
    public void push(E e) { addFirst(e); }
    public E pop() { return removeFirst(); }
    public int size() { lm.readLock(); return deque.size(); }
    public boolean isEmpty() { lm.readLock(); return deque.isEmpty(); }
    public boolean containsAll(Collection<?> c) { lm.readLock(); return deque.containsAll(c); }

    public Iterator<E> iterator() { return new StmIterator<E>(deque.iterator(), lm); }
    public Iterator<E> descendingIterator() {
        return new StmIterator<E>(deque.descendingIterator(), lm);
    }

    public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); }
    public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); }
    public boolean addAll(Collection<? extends E> c) { throw new UnsupportedOperationException(); }
    public boolean removeFirstOccurrence(Object o) { throw new UnsupportedOperationException(); }
    public boolean removeLastOccurrence(Object o) { throw new UnsupportedOperationException(); }
    
    public boolean contains(Object o) { lm.readLock(); return deque.contains((E)o); }
    public boolean remove(Object o) { throw new UnsupportedOperationException(); }
    public Object[] toArray() { lm.readLock(); return deque.toArray(); }
    public <T> T[] toArray(T[] a) { lm.readLock(); return deque.toArray(a); }

    public ArrayDeque<E> clone() { lm.readLock();
        if (deque instanceof ArrayDeque) {
            lm.readLock(); return ((ArrayDeque<E>)deque).clone();
        } else throw new UnsupportedOperationException();
    }

    public void addFirst(E e) {
        lm.writeLock();
        deque.addFirst(e);
        lm.logUndo(new Runnable(){public void run(){
            deque.removeFirst();
        }});
    }
    public void addLast(E e) {
        lm.writeLock();
        deque.addLast(e);
        lm.logUndo(new Runnable(){public void run(){
            deque.removeLast();
        }});

    }
    public boolean offerFirst(E e) {
        lm.writeLock();
        deque.offerFirst(e);
        lm.logUndo(new Runnable(){public void run(){
            deque.removeFirst();
        }});
        return true;
    }

    public boolean offerLast(E e) {
        lm.writeLock();
        deque.offerLast(e);
        lm.logUndo(new Runnable(){public void run(){
            deque.removeLast();
        }});
        return true;
    }
    public E removeFirst() {
        lm.writeLock();
        final E formerContents = deque.removeFirst();
        lm.logUndo(new Runnable(){public void run(){
            deque.addFirst(formerContents);
        }});
        return formerContents;
    }

    public E removeLast() {
        lm.writeLock();
        final E formerContents = deque.removeLast();
        lm.logUndo(new Runnable(){public void run(){
            deque.addLast(formerContents);
        }});
        return formerContents;
    }

    public E pollFirst() {
        lm.writeLock();
        final E formerContents = deque.pollFirst();
        if (formerContents != null)
            lm.logUndo(new Runnable(){public void run(){
                deque.addFirst(formerContents);
            }});
        return formerContents;
    }

    public E pollLast() {
        lm.writeLock();
        final E formerContents = deque.pollLast();
        if (formerContents != null)
            lm.logUndo(new Runnable(){public void run(){
               deque.addLast(formerContents);
            }});
        return formerContents;
    }

    public void clear() {
        lm.writeLock();
        final ArrayDeque<E> copy = new ArrayDeque<E>(deque.size());
        for (E element: deque) copy.addLast(element);
        deque.clear();
        lm.logUndo(new Runnable(){public void run(){
            for (E element: copy) deque.addLast(element);
        }});
    }

    public String toString() { return toString(deque); }
}